package de.mmis.core.base;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;

import de.mmis.core.base.event.Observable;
import de.mmis.core.base.gclass.GClass;

/**
 * Class representing an object descriptor providing information about a
 * published object
 * 
 * @author Sebastian Bader
 * @author Christian Koch
 */
public class ObjectDescriptor {

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public static @interface classHelp {
		String helpText();
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER,
			ElementType.PACKAGE, ElementType.CONSTRUCTOR, ElementType.FIELD,
			ElementType.ANNOTATION_TYPE })
	public static @interface Attributes {
		String[] value();
	}

	private final Hashtable<TypeVariable<?>, GClass> typevarmap; // maps type
																	// parameters
																	// of
																	// parameterized
																	// classes
																	// to actual
																	// types
																	// used
																	// (needed
																	// because
																	// of type
																	// erasure)
	private final Object object; // object wrapped, may be null
	private final Class<?> objectClass; // object class of object wrapped
	private final GClass gclass;
	private final List<MethodDescriptor> methodDescriptors; // method
															// descriptors of
															// object methods
	private final List<MethodDescriptor> getterMethodDescriptors; // method
																	// descriptors
																	// of all
																	// getters
	private final Class<?> observableClass;
	private final Class<?>[] interfaces; // all interfaces implemented by the
											// object
	private final String classHelp;

	public Class<?> getObservableClass() {
		return this.observableClass;
	}

	public Class<?>[] getObjectInterfaces() {
		return this.interfaces;
	}

	public Class<?> getObjectClass() {
		return this.objectClass;
	}

	/**
	 * creates new object descriptor with event observation enabled
	 * 
	 * @param object
	 *            object to collect information about
	 */
	public ObjectDescriptor(Object object) {
		this(object, object.getClass(), true,
				new Hashtable<TypeVariable<?>, GClass>());
	}

	/**
	 * creates new object descriptor with event observation enabled
	 * 
	 * @param object
	 *            object to collect information about
	 */
	public ObjectDescriptor(Object object, boolean enableObservation) {
		this(object, object.getClass(), enableObservation,
				new Hashtable<TypeVariable<?>, GClass>());
	}

	/**
	 * creates new object descriptor with event observation enabled
	 */
	public ObjectDescriptor(Class<?> objectClass) {
		this(null, objectClass, true, new Hashtable<TypeVariable<?>, GClass>());
	}

	/**
	 * creates new object descriptor with event observation enabled
	 */
	public ObjectDescriptor(Class<?> objectClass, boolean enableObservation) {
		this(null, objectClass, enableObservation,
				new Hashtable<TypeVariable<?>, GClass>());
	}

	/**
	 * creates new object descriptor with event observation enabled
	 * 
	 * @param object
	 *            object to collect information about
	 */
	public ObjectDescriptor(Object object, boolean enableObservation,
			Hashtable<TypeVariable<?>, GClass> typevarmap) {
		this(object, object.getClass(), enableObservation, typevarmap);
	}

	/**
	 * creates new object descriptor
	 * 
	 * @param object
	 *            object to collect information about
	 */
	public ObjectDescriptor(Object object, Class<?> objectClass,
			boolean enableObservation,
			Hashtable<TypeVariable<?>, GClass> typevarmap) {
		this.object = object;
		this.objectClass = objectClass;

		this.interfaces = Toolbox.getAllInterfaces(objectClass, null).toArray(
				new Class<?>[0]);

		this.methodDescriptors = new ArrayList<MethodDescriptor>();
		this.getterMethodDescriptors = new ArrayList<MethodDescriptor>();
		this.typevarmap = typevarmap; // new Hashtable<TypeVariable<?>,
										// GClass>();

		classHelp ca = objectClass.getAnnotation(classHelp.class);
		if (ca != null)
			this.classHelp = ca.helpText();
		else
			this.classHelp = null;

		this.gclass = GClass.fromType(objectClass, typevarmap);
		GClass.buildTypeTree(objectClass, this.typevarmap);

		if (enableObservation) {
			GClass[] tmp = GClass.getGenericsOf(this.gclass, Observable.class);
			if (tmp != null && tmp.length == 1 && tmp[0] != null) {
				Class<?> obsclass = tmp[0].getBaseClass();
				if (obsclass != null) {
					this.observableClass = obsclass;
					// observable=new ObservableType(obsclass,getObject());
					// System.out.println("Observable found, generic: "+observableClass.getSimpleName());
				} else
					this.observableClass = null;
			} else
				this.observableClass = null;
		} else
			this.observableClass = null;

		initFunctions();
	}

	/**
	 * auxiliary, converts given type to runtime class (removes parameters, maps
	 * type variables using built map)
	 * 
	 * @param pt
	 *            type to convert
	 * @return class of given type
	 */
	public GClass typeToClass(Type pt) {
		return GClass.fromType(pt, this.typevarmap);
	}

	/**
	 * creates short String representation of given type
	 * 
	 * @param pt
	 *            type to convert
	 * @return String representation of given type
	 */
	public String typeToShortString(Type pt) {
		return GClass.fromType(pt, this.typevarmap).toString();
	}

	/**
	 * auxiliary, creates method descriptors for object methods
	 */
	public void initFunctions() {
		Method[] allMethods = this.objectClass.getMethods();
		for (Method m : allMethods) {
			if (Modifier.isPublic(m.getModifiers())
					&& !Modifier.isStatic(m.getModifiers())) {
				MethodDescriptor methodDescriptor = new MethodDescriptor(this,
						m);
				this.methodDescriptors.add(methodDescriptor);
				if (methodDescriptor.isGetter())
					this.getterMethodDescriptors.add(methodDescriptor);
			}
		}

		//
		Collections.sort(this.methodDescriptors);
		Collections.sort(this.getterMethodDescriptors);
	}

	/**
	 * @return wrapped object
	 */
	public Object getObject() {
		return this.object;
	}

	/**
	 * @return list of method descriptors of this object
	 */
	public List<MethodDescriptor> getMethodDescriptors() {
		return this.methodDescriptors;
	}

	/**
	 * @return list of method descriptors of all getters
	 */
	public List<MethodDescriptor> getGetterMethodDescriptors() {
		return this.getterMethodDescriptors;
	}

	@Override
	public String toString() {
		return "ObjectDescriptor (" + this.object + ")";
	}

	public String getClassHelp() {
		return this.classHelp;
	}
}
